package com.aaa.bbb.ccc;

import lombok.Data;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author XJK
 * @create_time 2024/7/21
 */
public class PropertyMapper {

    public static void mapProperties(Object source, Object target) throws IllegalAccessException, InstantiationException {
        Map<String, List<Field>> targetFields = getAnnotatedFields(target.getClass());
        Map<String, Field> sourceFields = getAnnotatedSourceFields(source.getClass());

        for (Map.Entry<String, List<Field>> entry : targetFields.entrySet()) {
            String annotationValue = entry.getKey();
            List<Field> fields = entry.getValue();

            if (sourceFields.containsKey(annotationValue)) {
                Field sourceField = sourceFields.get(annotationValue);
                for (Field targetField : fields) {
                    Object sourceValue = sourceField.get(source);
                    if (sourceValue != null) {
                        MapProperty mapProperty = sourceField.getAnnotation(MapProperty.class);
                        Class<?> targetType = mapProperty.targetType();
                        Class<? extends Converter> converterClass = mapProperty.converter();

                        if (converterClass != Converter.class) {
                            Converter converter = converterClass.newInstance();
                            Object convertedValue = converter.convert(sourceValue);
                            targetField.set(target, convertedValue);
                        } else {
                            targetField.set(target, sourceValue);
                        }
                    }
                }
            }
        }
    }

    private static Map<String, List<Field>> getAnnotatedFields(Class<?> clazz) {
        Map<String, List<Field>> annotatedFields = new HashMap<>();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(MapProperty.class)) {
                field.setAccessible(true);
                String annotationValue = field.getAnnotation(MapProperty.class).value();
                annotatedFields.computeIfAbsent(annotationValue, k -> new ArrayList<>()).add(field);
            }
        }
        return annotatedFields;
    }

    private static Map<String, Field> getAnnotatedSourceFields(Class<?> clazz) {
        Map<String, Field> annotatedFields = new HashMap<>();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(MapProperty.class)) {
                field.setAccessible(true);
                String annotationValue = field.getAnnotation(MapProperty.class).value();
                annotatedFields.put(annotationValue, field);
            }
        }
        return annotatedFields;
    }

    public static void main(String[] args) {

        SourceClass source = new SourceClass();
        source.setField1(LocalDateTime.now());
        source.setField2("Value2");

        TargetClass target = new TargetClass();
        target.setTargetField2("aaaaa");

        try {
            PropertyMapper.mapProperties(source, target);
            System.out.println("Target targetField1: " + target.getTargetField1());
            System.out.println("Target anotherTargetField1: " + target.getAnotherTargetField1());
            System.out.println("Target targetField2: " + target.getTargetField2());
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }


    }


}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface MapProperty {
    String value();
    Class<?> targetType() default void.class;
    Class<? extends Converter> converter() default Converter.class;
}
interface Converter<S, T> {
    T convert(S source);
}
class DefaultConverter implements Converter<String, String> {
    @Override
    public String convert(String source) {
        return source;
    }
}

class LocalDateTimeToStringConverter implements Converter<LocalDateTime, String> {
    @Override
    public String convert(LocalDateTime source) {
        return source.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
}
@Data
class SourceClass {
    @MapProperty(value = "field1",targetType = String.class,converter = LocalDateTimeToStringConverter.class)
    private LocalDateTime field1;

    @MapProperty("field2")
    private String field2;

    // Getters and Setters
}
@Data
class TargetClass {
    @MapProperty("field1")
    private String targetField1;

    @MapProperty("field1")
    private String anotherTargetField1;

    @MapProperty("field2")
    private String targetField2;

    // Getters and Setters
}